/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.brillien.core.component.server;

import com.vii.brillien.core.component.*;
import com.vii.brillien.core.component.db.DataSource;
import com.vii.brillien.core.component.io.Logger;
import com.vii.brillien.core.management.BrillienServices;
import com.vii.brillien.core.management.CouchDBServices;
import com.vii.brillien.core.management.component.PresenceServices;
import com.vii.brillien.core.management.jmx.LiaisonMXBean;
import com.vii.brillien.ignition.BrillienContext;
import com.vii.brillien.ignition.transport.BrillienCommunication;
import com.vii.brillien.kernel.BrillienException;
import com.vii.brillien.kernel.annotations.PresenceService;
import com.vii.brillien.kernel.annotations.lifecycle.Consonant;
import com.vii.brillien.kernel.annotations.lifecycle.Resident;
import com.vii.brillien.kernel.annotations.lifecycle.Sparkle;
import com.vii.brillien.kernel.axiom.atomic.Commander;
import com.vii.brillien.kernel.axiom.atomic.Presence;
import com.vii.brillien.kernel.axiom.atomic.PresenceManager;
import com.vii.streamline.services.ThreadServices;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;

/**
 * Special SuperFlow type to provide the Commander services in a Brillien instance.
 */
@PresenceService( periodical = true, logLevel = PresenceService.CONFIG )
@Resident
public class Liaison<P extends PresenceManager, R> extends SuperFlow<P,R> implements Commander<P, BrillienCommunication,R>, LiaisonMXBean {

    protected String                        ssoPresenceName;
    protected String                        apiKeyPresenceName;

    /**
     * PrimordialPresenceManagers dealing with PresenceManager types
     */
    protected ConcurrentLinkedQueue<P>      primordialPresenceManagers;

    /**
     * PresenceManagers dealing with Presence types
     */
    protected ConcurrentLinkedQueue<P>      PresenceManagers;

    {
        primordialPresenceManagers = new ConcurrentLinkedQueue<P>();

        PresenceManagers = new ConcurrentLinkedQueue<P>();

        periodical = true;

        name = "Liaison";

        waitingForInputData = true;
    }

    @Override
    public String getSSOPresenceName(){
        return ssoPresenceName;
    }

    @Override
    public void setSSOPresenceName(String PresenceName) {
        if( PresenceName != null )
            this.ssoPresenceName = PresenceName;
    }

    @Override
    public String getApiKeyPresenceName() {
        return apiKeyPresenceName;
    }

    @Override
    public void setApyKeyPresenceName( String PresenceName ){
        if( PresenceName != null )
            this.apiKeyPresenceName = PresenceName;
    }

    @Override
    protected R innerCall() throws BrillienException{
        BrillienContext.systemLog( Level.FINE, "Bip... Bip... Bip..." );
        CouchDBServices.heartBeat();
        return null;
    }

    protected <T extends Presence> String initPresenceManager( PresenceManager manager, String presenceName, Class<T> Presence ) throws BrillienException {
        manager.initPresenceManager( presenceName, Presence );

        BrillienContext.createAccountFor( manager.getName() );

        manager.activatePresenceManager();

        //BrillienContext.newXMPPContact( manager.getName() );

        addPresences( (P)manager );

        BrillienContext.systemLog( Level.FINE, "Registered Presence with new manager associated::" + manager.getClass() + " For presenceClass::" + Presence );

        return manager.getName();
    }

    protected PresenceManager getPresenceManagerOf( String presenceName, Class Presence ){
        for( PresenceManager primordialPresenceManager : primordialPresenceManagers ){
            if(
                (presenceName != null && primordialPresenceManager.getName().equalsIgnoreCase(presenceName)) ||
                (Presence != null && primordialPresenceManager.getPresenceClass().getName().equalsIgnoreCase( Presence.getName() ) )
            ){
                return primordialPresenceManager;
            }
        }
        for( PresenceManager presenceManager : PresenceManagers ){
            if(
                (presenceName != null && presenceManager.getName().equalsIgnoreCase(presenceName)) ||
                (Presence != null && presenceManager.getPresenceClass().getName().equalsIgnoreCase( Presence.getName() ) )
            ){
                return presenceManager;
            }
        }
        return null;
    }

    @Override
    public P getPresenceManagerOf(String name) {
        return (P) getPresenceManagerOf( name, null );
    }
    
    /**
     * Regiters a PresenceManager. It is performed after loading a presence manager component from the deployment directory.
     * @param Presence PresenceManager type
     * @param <T> Generic type of the presence
     * @return Name of the primordial presencemanager dealing with this PresenceManager type
     * @throws BrillienException
     */
    protected <T extends Presence> String registerPresenceManager( String presenceName, Class<T> Presence ) throws BrillienException {
        if( Presence == null )
            throw new BrillienException("Null class reference received!" );

        if( !Presence.isAnnotationPresent( Consonant.class ) && !Presence.isAnnotationPresent( Resident.class ) && !Presence.isAnnotationPresent( Sparkle.class ) )
            throw new BrillienException("Invalid Presence type: " + Presence + " :: Not convenient instantiation annotation present!");

        PresenceManager mng = getPresenceManagerOf( presenceName, Presence );
        if( mng != null && ( presenceName==null || presenceName.equals(mng.getName()) ) ){
            BrillienContext.systemLog( Level.CONFIG, "Already published Presence::" + mng.getName() );
            return mng.getName();
        }

        PresenceManager manager = new PrimordialManager( );

        initPresenceManager( manager, null, Presence );

        primordialPresenceManagers.add( (P)manager );

        log("Registered: " + manager.getName());
        System.out.println("Registered: " + manager.getName());

        return manager.getName();
    }

    /**
     * Regiters a Presence Component. It is performed after loading a presence type from the deployment directory.
     * @param Presence Presence type
     * @param <T> Generic type of the presence
     * @return Name of the presencemanager dealing with this PresenceManager type
     * @throws BrillienException
     */                                         
    protected <T extends Presence> String registerServicePresence( String presenceName, Class<T> Presence ) throws BrillienException {
        if( Presence == null )
            throw new BrillienException("Null class reference received!" );

        if( !Presence.isAnnotationPresent( PresenceService.class ) && !SuperPresence.class.isAssignableFrom( Presence ) && !Logger.class.isAssignableFrom( Presence ) ){
            throw new BrillienException("Invalid Presence type: " + Presence + " :: No presence annotation or convenient superclass present!");
        }
        /*
        if( !Presence.isAnnotationPresent( PresenceService.class ) )
            throw new BrillienException("Invalid Presence type: " + Presence + " :: No presence annotation present!");

        if( !SuperPresence.class.isAssignableFrom( Presence ) && !Logger.class.isAssignableFrom( Presence ) )
            throw new BrillienException("Invalid Presence type: " + Presence + ". Not a convenient superclass!");
        */
        PresenceManager mng = getPresenceManagerOf( presenceName, Presence );
        if( mng != null && ( presenceName==null || presenceName.equals(mng.getName()) ) ){
            BrillienContext.systemLog( Level.CONFIG, "Already published Presence::" + mng.getName() );
            return mng.getName();
        }

        String managerName =
                DataSource.class.isAssignableFrom( Presence )
                ? "DataSourceManager"
                : SuperFlow.class.isAssignableFrom( Presence )
                ? "SuperFlowManager"
                : SuperUnit.class.isAssignableFrom( Presence )
                ? "SuperUnitManager"
                : "SuperPresenceManager";
        if( Presence.isAnnotationPresent( PresenceService.class ) ){
            PresenceService p = Presence.getAnnotation( PresenceService.class );
            if( p != null && p.presenceManager() != null && !p.presenceManager().equals("") )
                managerName = p.presenceManager();
        }
        PresenceManager manager = (PresenceManager)( getPresenceManagerOf( managerName ).getInstance( this ) );

        initPresenceManager( manager, presenceName, Presence );

        PresenceManagers.add( (P)manager );

        log("Registered: " + manager.getName() );
        System.out.println("Registered: " + manager.getName() );

        return manager.getName();
    }

    @Override
    public <T extends Presence> String registerPresence(Class<T> Presence) throws BrillienException {
        String PresenceName = PresenceServices.getPresenceName( Presence );
        PresenceManager manager = getPresenceManagerOf( PresenceName, null );
        if( manager != null )
            throw new BrillienException( "A Presence has been already published with this name : " + PresenceName );

        return  AbstractPresenceManager.class.isAssignableFrom( Presence )
            ? registerPresenceManager( null, Presence )
            : registerServicePresence( null, Presence );
    }

    @Override
    public String registerPresence(String originalName, String cloneName) throws BrillienException {
        PresenceManager manager = getPresenceManagerOf( originalName, null );

        if( manager == null )
            throw new BrillienException( "Cannot found original published presence with name::" + originalName );

        return registerPresence( manager.getPresenceClass(), cloneName );
    }

    @Override
    public <T extends Presence> String registerPresence(Class<T> Presence, String cloneName) throws BrillienException {
        PresenceManager manager = getPresenceManagerOf( null, Presence );

        if( manager == null )
            throw new BrillienException( "Cannot found original published presence with class::" + Presence.getName() );

        return  AbstractPresenceManager.class.isAssignableFrom( Presence )
                ? registerPresenceManager( cloneName, Presence )
                : registerServicePresence( cloneName, Presence );
    }

    @Override
    public void unregisterPresence(String PresenceName) throws BrillienException {
        PresenceManager manager = getPresenceManagerOf( PresenceName );

        manager.passivateAll();

        primordialPresenceManagers.remove( manager );
        PresenceManagers.remove( manager );

        removePresences( (P)manager );

        BrillienContext.systemLog( Level.CONFIG, "UnRegistered Presence::" + manager.getName() );
    }

    @Override
    public Collection<String> getPresenceNames() {
        List<String> names = new ArrayList<String>( PresenceManagers.size() );
        for( PresenceManager pm : PresenceManagers )
            names.add( pm.getName() );
        return names;
    }

    @Override
    public boolean hasPresence(String name) {
        return getPresenceManagerOf(name) != null;
    }

    @Override
    public Collection<P> getPresenceManagers() {
        return PresenceManagers; 
    }

    // minden send null legyen...
    
    /*
        -----------------------------------------------------------------------------
        ------------------------ JMX methods ----------------------------------------
        -----------------------------------------------------------------------------
     */

    @Override
    public String[][] getConfigProperties() {
        return BrillienContext.configuration;
    }

    @Override
    public String[] getPublishedPresencesNames() {
        List<String> l = new LinkedList<String>();
        for (P manager : PresenceManagers) {
            l.add( manager.getName() );
        }
        return l.toArray( new String[ l.size() ] );
    }

    @Override
    public String[] getPresenceStateSpace(String PresenceName) {
        return new String[0];  
    }

    @Override
    public Double getServerVersion() {
        return BrillienContext.SERVER_VERSION;
    }

    @Override
    public void shutDown() {
        ThreadServices.scheduleSingleTask(
                new TimerTask(){
                    @Override
                    public void run() {
                        try {
                            BrillienServices.stopServices();
                        } catch (BrillienException e) {
                            exceptionLog( "LiaisonWS", "shutDown", e );
                        }
                    }
                },
                500
        );
    }

}
